package com.h3bpm.starcharge.service.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import OThinker.Common.DateTimeUtil;
import OThinker.Common.DotNetToJavaStringHelper;
import OThinker.H3.Entity.Instance.Data.InstanceData;
import com.h3bpm.starcharge.common.uitl.DateUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import com.h3bpm.base.operator.UserInfoOperator;
import com.h3bpm.base.res.ResBody;
import com.h3bpm.base.user.UserValidator;
import com.h3bpm.base.user.UserValidatorHolder;
import com.h3bpm.base.util.AppUtility;
import com.h3bpm.starcharge.common.bean.WorkflowComment;
import com.h3bpm.starcharge.common.bean.WorkflowCommentAttachment;
import com.h3bpm.starcharge.common.uitl.BestsignPropertiesUtil;
import com.h3bpm.starcharge.common.uitl.DBConfigUtil;
import com.h3bpm.starcharge.common.uitl.FileUploadUtil;
import com.h3bpm.starcharge.service.WorkflowCommentService;

import OThinker.Common.Organization.Models.Unit;
import OThinker.Common.Organization.Models.User;
import OThinker.H3.Entity.IEngine;
import OThinker.H3.Entity.Notification.Notification;
import OThinker.H3.Entity.Notification.NotifyType;
import OThinker.H3.Entity.Settings.CustomSetting;
import OThinker.H3.Entity.Settings.ISettingManager;
import OThinker.H3.Entity.WorkItem.IWorkItemManager;
import OThinker.H3.Entity.WorkItem.NotificationParser;

/**
 * @author qinyt
 * @className WorkflowCommentServiceImpl
 * @description workflow评论服务实现类
 * @date 2019/3/1 14:51
 **/
@Service
public class WorkflowCommentServiceImpl implements WorkflowCommentService {
    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowCommentServiceImpl.class);

    /**
     * 新增评论的sql
     */
    private static String insertCommentSql = BestsignPropertiesUtil.getProperty("insertCommentSql");

    /**
     * 新增评论附件/图片的sql
     */
    private static String insertCommentAttachSql = BestsignPropertiesUtil.getProperty("insertCommentAttachmentSql");

    /**
     * 查询评论内容sql（不包含附件内容）
     */
    private static String queryCommentSql = BestsignPropertiesUtil.getProperty("queryCommentSql");

    /**
     * 查询评论附件内容sql
     */
    private static String queryCommentAttachmentSql = BestsignPropertiesUtil.getProperty("queryCommentAttachmentSql");

    /**
     * 获取配置文件中设置的钉钉消息推送标题
     */
    //private static String DingTalkMessageTitle = BestsignPropertiesUtil.getProperty("DingTalkMessageTitle");您有新的评论

    private static String DingTalkMessageTitle = "您有新的评论";

    private static String DingTalkEndMessage = "您有新的流程已结束";


    @Autowired
    private UserInfoOperator userInfoOperator;

    @Override
    public ResBody addComment(WorkflowComment comment) {
        ResBody result = ResBody.buildFailResBody();
        // 验证参数是否合法


        // 写数据到db
        boolean flag = writeCommentToDB(comment);
        if (!flag) {
            result.setExtendMsg("提交失败。");
            return result;
        }

        // 异步发送消息
        //sendMsgToUser(comment);

        return ResBody.buildSuccessResBody();
    }

    @Override
    public ResBody getComments(String instanceId) {
        ResBody result = ResBody.buildFailResBody();

        try {
            Map<String, WorkflowComment> commentMap = getCommentMap(instanceId);
            if (MapUtils.isEmpty(commentMap)) {
                return ResBody.buildSuccessResBody();
            }
            
            List<WorkflowComment> commentList = new ArrayList<>();
            for (Entry<String,WorkflowComment> entry : commentMap.entrySet()) {
                commentList.add(entry.getValue());
            }
            commentList.sort((WorkflowComment c1, WorkflowComment c2) -> c2.getGmtCreate().compareTo(c1.getGmtCreate()));
            return ResBody.buildSuccessResBody(commentList);
        } catch (Exception e) {
            result.setExtendMsg("获取评论内容失败。");
            return result;
        }
    }

    @Override
    public ResBody uploadAttachment(MultipartFile file, boolean uploadByImage) {
        ResBody result = ResBody.buildFailResBody();
        // 验证要上传的文件是否合法
        String errMsg = validateFile(file, uploadByImage);
        if (StringUtils.isNotEmpty(errMsg)) {
            result.setExtendMsg(errMsg);
            return result;
        }

        // 生成临时文件名
        String id = UUID.randomUUID().toString();

        String fileName = file.getOriginalFilename();
        // 得到文件扩展名
        String extension = FilenameUtils.getExtension(fileName);

        String tmpFileName = id;
        // 上传文件有后缀名
        if (StringUtils.isNotEmpty(extension)) {
            tmpFileName += FilenameUtils.EXTENSION_SEPARATOR + extension;
        }

        String path = FileUploadUtil.getFilePath(tmpFileName);
        try {
            FileUploadUtil.writeToFile(file, path);
        } catch (IOException ioe) {
            errMsg = "文件上传失败";
            LOGGER.error(errMsg);
            result.setExtendMsg(errMsg);
            return result;
        }
        String url = "TempFiles" + File.separator+ AppUtility.getEngine().getEngineConfig().getCode() + File.separator + tmpFileName;

        // 构建返回对象
        WorkflowCommentAttachment attachment = new WorkflowCommentAttachment();
        attachment.setId(id);
        attachment.setDisplay(uploadByImage);
        attachment.setFileName(fileName);
        attachment.setContentType(file.getContentType());
        attachment.setContentLength(file.getSize());
        attachment.setFileExtension(extension);
        attachment.setUrl(url);

        result = ResBody.buildSuccessResBody(attachment);

        return result;
    }

    @Override
    public ResBody sendEndMessage(String instanceId) {
        try {
            InstanceData instanceData = new InstanceData(AppUtility.getEngine(),instanceId,User.AdministratorID);
            User  user = (User)AppUtility.getEngine().getOrganization().GetUnit(instanceData.getInstanceContext().getOriginator());

            String dingTalkContent = "您的" + instanceData.getInstanceContext().getInstanceName() + "流程在"+ DateTimeUtil.format(new Date())+"结束,请知晓！";
            Notification notification = new Notification(NotifyType.DingTalk, null, null, user.getDingTalkAccount(), null,
                    -1, instanceId, DingTalkEndMessage, dingTalkContent,
                    null);
            AppUtility.getEngine().getNotifier().Notify(notification);
           return ResBody.buildSuccessResBody();
        } catch (Exception e) {
            return  ResBody.buildFailResBody();
        }
    }

    /**
     * 检查文件是否符合要求
     * 
     * @param file
     * @param uploadByImage
     * @return String 如果符合要求则返回null，否则返回错误信息
     */
    private String validateFile(MultipartFile file, boolean uploadByImage) {
        if (null == file || file.getSize() == 0) {
            String errMsg = "上传文件不能为空";
            LOGGER.error(errMsg);
            return errMsg;
        }

        int allowedSize = FileUploadUtil.getAllowedMaxSize(uploadByImage);

        // 当上传文件大小超过指定大小限制后
        if (file.getSize() > allowedSize) {
            String errMsg = "上传文件大小超过最大限制";
            LOGGER.error(errMsg);
            return errMsg;
        }

        return null;
    }

    /**
     * 验证参数，返回错误信息或者null/空
     * 
     * @param comment
     * @return
     */
    private String validateParams(WorkflowComment comment) {
        if (null == comment) {
            return "评论不能为空";
        }

        return validateParams(comment.getWorkItemId(), comment.getSheetCode());
    }

    /**
     * 验证参数，返回错误信息或者null/空
     * 
     * @param workitemId
     * @param sheetCode
     * @return
     */
    private String validateParams(String workitemId, String sheetCode) {

        if (StringUtils.isEmpty(workitemId) || StringUtils.isEmpty(sheetCode)) {
            return "workitemId 和 sheetCode 不能为空。";
        }

        return null;
    }

    /**
     * 把评论内容和附件（如果有）写入db
     * 
     * @param comment
     * @return 写入db成功则返回true否则返回false
     */
    private boolean writeCommentToDB(WorkflowComment comment) {
        Connection conn = null;
        PreparedStatement insertCommentPs = null;
        PreparedStatement insertAttachmenPs = null;
        boolean success = false; // 是否成功
        try {
            conn = getDefaultConnection();
            conn.setAutoCommit(false);
            insertCommentPs = conn.prepareStatement(insertCommentSql);
            insertAttachmenPs = conn.prepareStatement(insertCommentAttachSql);

            // 生成主键
            String commentId = UUID.randomUUID().toString();
            insertCommentPs.setString(1, commentId);
            insertCommentPs.setString(2, comment.getWorkItemId());
            insertCommentPs.setString(3, comment.getSheetCode());
            insertCommentPs.setString(4, comment.getContent());
            insertCommentPs.setString(5, comment.getUserId());
            insertCommentPs.setString(6, comment.getUserName());
            insertCommentPs.setString(7, userInfoOperator.GetUserImageUrl(userInfoOperator.GetUserInfo(comment.getUserId())));
            insertCommentPs.setString(8, comment.getNotifyUserIds());
            insertCommentPs.setString(9, comment.getInstanceId());

            // 插入评论
            insertCommentPs.execute();

            HttpServletRequest request =
                    ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String currentPath = request.getServletContext().getRealPath("/")+ File.separator + "Portal" + File.separator;

            // 获取所有附件，并插入。因附件不会太多，故此处没有分批提交和批量更新
            List<WorkflowCommentAttachment> attachments = comment.getAttachments();
            if (CollectionUtils.isNotEmpty(attachments)) {
                for (WorkflowCommentAttachment attachment : attachments) {
                    String url = currentPath + attachment.getUrl();
                    File file = new File(url);
                    if (!file.exists() || file.length() <= 0) {
                        continue;
                    }

                    // 获得文件内容
                    byte[] bytes = FileUtils.readFileToByteArray(file);

                    insertAttachmenPs.setString(1, attachment.getId());
                    insertAttachmenPs.setString(2, commentId);
                    insertAttachmenPs.setBytes(3, bytes);
                    insertAttachmenPs.setString(4, attachment.getContentType());
                    insertAttachmenPs.setLong(5, attachment.getContentLength());
                    insertAttachmenPs.setString(6, attachment.getFileName());
                    insertAttachmenPs.setString(7, attachment.getFileExtension());
                    insertAttachmenPs.setBoolean(8, attachment.isDisplay());
                    insertAttachmenPs.setString(9,attachment.getUrl());

                    insertAttachmenPs.execute();
                }
            }

            conn.commit();
            success = true;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.warn(e.getMessage());
            try {
                conn.rollback();
            } catch (SQLException sqle) {
                LOGGER.warn(sqle.getMessage());
            }
        } finally {

            
            List<PreparedStatement> psList = new ArrayList<>();
            psList.add(insertCommentPs);           
            psList.add(insertAttachmenPs);
            
            closeConnection(null, psList, conn);
        }

        return success;
    }
    
    private Connection getDefaultConnection() {
        return DBConfigUtil.getConn("com.microsoft.jdbc.sqlserver.SQLServerDriver", "Engine");
    }
    
    private void closeConnection(List<ResultSet> rsList, List<PreparedStatement> stmtList, Connection conn) {
        try {
            if (CollectionUtils.isNotEmpty(rsList)) {
                for (ResultSet rs :  rsList) {
                    if (null != rs) {
                        rs.close();
                    }
                }
            }
            
            if (CollectionUtils.isNotEmpty(stmtList)) {
                for (PreparedStatement stmt :  stmtList) {
                    if (null != stmt) {
                        stmt.close();
                    }
                }
            }
            
            DBConfigUtil.closeConn(conn);
            
        } catch (SQLException sqle) {
            LOGGER.warn(sqle.getMessage());
        }
    }

    /**
     * @param comment
     */
    public void sendMsgToUser(WorkflowComment comment) {
        if (StringUtils.isNotEmpty(comment.getNotifyUserIds())) {
            try {                
                IEngine engine = AppUtility.getEngine();                
                ISettingManager settingManager = engine.getSettingManager();
                String workItemId = comment.getWorkItemId();                
                
                // 在评论界面@用户后，需要推送的用户的钉钉账号列表。此处不包含用户本身（因为要推送的用户可能没有参与当前流程的权限，故此处需要先传阅
                // 给这些用户，以生成新的任务id，此后才能使用这些新生成的id，给相应用户推送消息，否则没有参与当前流程权限的用户将无法打开表单）
                List<String> notifyDDAccountList = new ArrayList<>();
                // 需要传阅的用户的Id列表
                List<String> userIdList = new ArrayList<>();
                // 是否需要通知自己
                boolean notifySelf = false;

                if (!DotNetToJavaStringHelper.isNullOrEmpty(comment.getNotifyUserIds()) && comment.getNotifyUserIds().indexOf(comment.getUserId()) >= 0) {
                    notifySelf = true;
                }
                
                //根据NotifyUserIds 获取用户信息 再获取DingTalkAccount 用于消息推送
                List<Unit> units = engine.getOrganization().GetUnits(Arrays.asList(comment.getNotifyUserIds().split(",")));
                
                // 构建需要传阅的用户Id列表和需要发送钉钉通知的用户钉钉账号列表
                buildNotifyAccount(units, notifyDDAccountList, userIdList,comment.getUserId());
                
                if (CollectionUtils.isNotEmpty(userIdList)) {
                    String[] receivers = new String[userIdList.size()];
                    userIdList.toArray(receivers);

                   IWorkItemManager workItemManager = engine.getWorkItemManager();
                    // 传阅并返回新的任务Id列表
                    String[] taskIdArray = workItemManager.Circulate(comment.getUserId(), workItemId, receivers, false, "");
                    
                    if (taskIdArray.length != notifyDDAccountList.size()) {
                        // 应该为每一个需要通知的用户生成一条新的任务Id
                        throw new Exception("传阅任务数和钉钉通知的列表数量不匹配。");
                    }
                    
                    String dDUrl = settingManager.GetCustomSetting(CustomSetting.Setting_DDUrl);
                    String dDPcUrl = settingManager.GetCustomSetting(CustomSetting.Setting_DDPcUrl);
                    
                    String realDDUrl = null;
                    String realDDpcUrl = null;
                    for (int i = 0; i < taskIdArray.length; i++) {
                        String taskId = taskIdArray[i];
                        //获取钉钉推送消息 APPUrl PCUrl
                        realDDUrl = dDUrl.replace(NotificationParser.Tag_WorkItemID, taskId);
                        realDDpcUrl = dDPcUrl.replace(NotificationParser.Tag_WorkItemID, taskId);
                        
                        Notification notification = new Notification(NotifyType.DingTalk, null, null, notifyDDAccountList.get(i), null,
                            -1, comment.getInstanceId(), DingTalkMessageTitle, comment.getContent(), null, realDDUrl, realDDpcUrl,
                            null);
                        engine.getNotifier().Notify(notification);
                    }
                    
                    // 通知自己
                    if (notifySelf) {
                        realDDUrl = dDUrl.replace(NotificationParser.Tag_WorkItemID, workItemId);
                        realDDpcUrl = dDPcUrl.replace(NotificationParser.Tag_WorkItemID, workItemId);
                        String dingTalkAccount = ((User)AppUtility.getEngine().getOrganization().GetUnit(comment.getUserId())).getDingTalkAccount();
                        Notification notification = new Notification(NotifyType.DingTalk, null, null, dingTalkAccount, null,
                            -1, comment.getInstanceId(), DingTalkMessageTitle, comment.getContent(), null, realDDUrl, realDDpcUrl,
                            null);
    
                        engine.getNotifier().Notify(notification);
                    }
                }

            } catch (Exception e) {
                LOGGER.warn(e.getMessage());
            }
        }

    }

    private Map<String, WorkflowComment> getCommentMap(String instanceId) throws Exception {
        Connection conn = null;
        PreparedStatement queryCommentPs = null;
        PreparedStatement queryCommentAttachPs = null;
        ResultSet resultSet = null;
        ResultSet attachResultSet = null;
        
        try {
            conn = getDefaultConnection();
            queryCommentPs = conn.prepareStatement(queryCommentSql);
            queryCommentAttachPs = conn.prepareStatement(queryCommentAttachmentSql);

            queryCommentPs.setString(1, instanceId);

            resultSet = queryCommentPs.executeQuery();
            
            // 评论的Map集合
            Map<String, WorkflowComment> commentMap = new HashMap<>();
            while (resultSet.next()) {
                String id = resultSet.getString(1); // 评论的id
                // 如果该评论已经存在，则只处理其附件内容
                if (commentMap.containsKey(id)) {
                    WorkflowCommentAttachment attachment = buildAttachment(resultSet);
                    if (null != attachment) {
                        // 把附件添加到评论对象中
                        attachment.setCommentId(id);
                        commentMap.get(id).getAttachments().add(attachment);
                        
                        writeAttachmentToFile(queryCommentAttachPs, attachResultSet, attachment);
                    }
                    
                } else {
                    // 如果该评论不存在
                    // 构建新的评论对象
                    WorkflowComment comment = buildComment(resultSet);
                    comment.setId(id); // id
                    
                    // 构建附件对象（不包括附件内容）
                    WorkflowCommentAttachment attachment = buildAttachment(resultSet);
                    List<WorkflowCommentAttachment> attachList = new ArrayList<>();

                    if (null != attachment) { 
                        attachment.setCommentId(id);
                        attachList.add(attachment);
                        writeAttachmentToFile(queryCommentAttachPs, attachResultSet, attachment);
                    }
                    
                    // 设置评论对象的附件
                    comment.setAttachments(attachList);
                    // 把该评论放入Map中
                    commentMap.put(id, comment);
                }
            }
            return commentMap;
        } catch (SQLException | IOException e) {
            LOGGER.error(e.getMessage());
            throw new Exception("获取评论失败。");
        } finally {
            List<ResultSet> rsList = new ArrayList<>();
            rsList.add(resultSet);
            rsList.add(attachResultSet);
            
            List<PreparedStatement> psList = new ArrayList<>();
            psList.add(queryCommentPs);
            psList.add(queryCommentAttachPs);
            
            closeConnection(rsList, psList, conn);
        }
    }

    /**
     * @param queryCommentAttachPs
     * @param attachment
     * @throws SQLException 
     * @throws IOException 
     */
    private boolean writeAttachmentToFile(PreparedStatement queryCommentAttachPs, ResultSet attachResultSet,
        WorkflowCommentAttachment attachment) throws SQLException, IOException {
        String tmpFileName = attachment.getId();
        String extension = attachment.getFileExtension();
        // 上传文件有后缀名
        if (StringUtils.isNotEmpty(extension)) {
            tmpFileName += FilenameUtils.EXTENSION_SEPARATOR + extension;
        }
        String path = FileUploadUtil.getFilePath(tmpFileName);

        File file = new File(path);
        if (file.exists()) {
            return true;
        }
        
        // 查询数据库拿到附件内容
        queryCommentAttachPs.setString(1, attachment.getId());
        attachResultSet = queryCommentAttachPs.executeQuery();

        InputStream is = null;
        OutputStream out = null;
        try {
            // 如果有附件，写入文件
            if (attachResultSet.next()) {
                is = attachResultSet.getBinaryStream(4);

                out = new FileOutputStream(file);
                byte[] bytes = new byte[1024];
                while (is.read(bytes, 0, 1024) != -1) {
                    out.write(bytes);
                }
            }
            return true;
        } catch (IOException ioe) {
            LOGGER.error(ioe.getMessage());
            return false;
        } finally {
                if (null != out) {
                    out.close();
                }
                if (null != is) {
                    is.close();
                }
        }
    }

    /**
     * 
     * @param resultSet
     * @return WorkflowComment
     * @throws SQLException
     */
    private WorkflowComment buildComment(ResultSet resultSet) throws SQLException {
        WorkflowComment comment = new WorkflowComment();

        comment.setWorkItemId(resultSet.getString(2)); // workitemId
        comment.setSheetCode(resultSet.getString(3)); // sheetCode
        comment.setContent(resultSet.getString(4)); // content
        comment.setUserId(resultSet.getString(5)); // userId
        comment.setUserName(resultSet.getString(6)); // userName
        comment.setUserAvator(resultSet.getString(7)); // userAvator
        comment.setNotifyUserIds(resultSet.getString(8)); // notifyUserIds
        //comment.setGmtCreate(resultSet.getLong(9)); // gmtCreate
        comment.setGmtCreate(resultSet.getTimestamp(9));
        comment.setInstanceId(resultSet.getString("instanceId"));

        return comment;
    }

    /**
     * 
     * @param resultSet
     * @return 评论的附件对象，如果为空返回null
     * @throws SQLException
     */
    private WorkflowCommentAttachment buildAttachment(ResultSet resultSet) throws SQLException {
        // 附件id
        String attachId = resultSet.getString(10);
        if (StringUtils.isNotEmpty(attachId)) {
            WorkflowCommentAttachment attachment = new WorkflowCommentAttachment();
            attachment.setId(attachId);

            boolean display = resultSet.getBoolean(11);
            String contentType = resultSet.getString(12);
            long contentLength = resultSet.getLong(13);
            String fileName = resultSet.getString(14);
            String extension = resultSet.getString(15);
            String url = resultSet.getString("url");

            attachment.setDisplay(display);
            attachment.setContentType(contentType);
            attachment.setContentLength(contentLength);
            attachment.setFileName(fileName);
            attachment.setFileExtension(extension);
            attachment.setUrl(url);
            
            return attachment;
        }
        
        return null;
        
    }
    
    /**
     * 构建需要传阅和发送通知的用户列表
     * @param units 评论中@的用户
     * @param notifyDDAccountList 需要发送钉钉通知的钉钉账号Id列表
     * @param userIdList 需要传阅的用户的Id列表
     */
    private void buildNotifyAccount(List<Unit> units, List<String> notifyDDAccountList, List<String> userIdList,String loginUserId) {
        
        for (Unit unit : units) {
            if (unit instanceof User) {
                User tmpUser = (User) unit;
                String userId = tmpUser.getObjectId();

                if (loginUserId.equals(userId)) { // 如果需要通知自己
                    continue;
                }
                // 如果当前用户不在列表中
                if (!userIdList.contains(userId)) {
                    userIdList.add(userId);
                    notifyDDAccountList.add(tmpUser.getDingTalkAccount());
                }
            }
        }
    }
}
